package com.googlecode.cswish.oosearch;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.googlecode.cswish.model.PageElement;
import com.googlecode.cswish.model.PageElements;
import com.googlecode.cswish.model.Saas;
import com.googlecode.cswish.struts.ContextHolder;
import com.googlecode.cswish.util.ClassUtil;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.util.ValueStack;

/**
 * @author Jerry.Feng Date: 2005-2-27 22:14:28
 * @version $id: $
 */
public class QLInfo implements Serializable {
	
	private transient final static Logger logger = Logger.getLogger(QLInfo.class);
	
	private String ql;
	private List<String> paramNames;
	private List<Object> params;
	private int firstResult;
	private int maxResults;
	private String compiledSort;		// sort field, must start with table alias, such as 'a.xxx'
	private String dir = "ASC";
	
	private int defaultCount = -1;
	
	private boolean permissionFilterExisted;

	public QLInfo() {
	}
	
	public QLInfo(String ql, boolean disablePermissionCheck) {
		this.ql = ql;
		
		buildSaasSql(ql);
		
		this.permissionFilterExisted = disablePermissionCheck;
	}
	
	private void buildSaasSql(String ql){
		if (ql.indexOf("a.id=") >= 0
				|| ql.indexOf("a.sid=") >= 0) return ;
		
		int begin = ql.indexOf("from ");
		int end = ql.lastIndexOf(" a ");
		if (begin >= 0 ){
			// end < 0 : "from user a"
			// end < begin : "select a from user a where a.priv=?"
			if (end < 0 || end < begin){
				end = ql.lastIndexOf(" a");
			}
			String clazzName = ql.substring(begin + 4, end).trim();
			Class clazz = ClassUtil.getClass(clazzName);
			if (clazz != null && Saas.class.isAssignableFrom(clazz)){
				int pos = ql.indexOf("where");
				if (pos > 0){
					String where = " where a.sid=" + ContextHolder.get().getSid() + " and ";
					this.ql = ql.replace(" where ", where);
				} else {
					String where = " where a.sid=" + ContextHolder.get().getSid();
					int orderPos = ql.indexOf(" order ");
					
					if (orderPos >= 0){
						this.ql = ql.replace(" order ", where + " order ");
					} else {
						int groupPos = ql.indexOf(" group ");
						if (groupPos >= 0){
							this.ql = ql.replace(" group ", where + " group ");
						} else {
							this.ql = ql + where;
						}
					}
				}
			} 
		}
	}
	
	public QLInfo(String ql, boolean disablePermissionCheck, Object ... params) {
		this.ql = ql;	
		
		buildSaasSql(ql);

		this.permissionFilterExisted = disablePermissionCheck;
		if (params != null) {
			if (params.length == 1 && params[0] instanceof List) {
				this.params = (List<Object>) params[0];
			} else {
				this.params = Arrays.asList(params);
			}
		}
	}
	
	public QLInfo(String ql, List<Object> params) {
		this.ql = ql;
		buildSaasSql(ql);
		this.params = params;
	}
	
	public QLInfo(String ql, boolean disablePermissionCheck, List<Object> params) {
		this.ql = ql;
		buildSaasSql(ql);
		this.permissionFilterExisted = disablePermissionCheck;
		this.params = params;
	}
	
	public void setPageInfo(int start, int limit, String sort, String dir) {
		setFirstResult(start);
		setMaxResults(limit);
		setCompiledSort(sort);
		setDir(dir);
	}
	
	public int getFirstResult() {
		return firstResult;
	}
	public void setFirstResult(int firstResult) {
		this.firstResult = firstResult;
	}
	public String getQl() {
		return ql;
	}
	public void setQl(String ql) {
		this.ql = ql;
		buildSaasSql(ql);
	}
	public String getQlWithSort() {
		String qlWithSort;
		if (hasSort()) {
    		int index = ql.indexOf(" order by ");
    		if (index > 0) {
    			qlWithSort =  ql + ',' + getSortAndDir();
    		} else {
    			qlWithSort = ql + " order by " + getSortAndDir();
    		}
    	} else {
    		qlWithSort = ql;
    	}
		return qlWithSort;
	}
	
	public List<String> getParamNames() {
		return paramNames;
	}

	public void setParamNames(List<String> paramNames) {
		this.paramNames = paramNames;
	}

	public List<Object> getParams() {
		return params;
	}
	
	public void setParams(List<Object> qlParams) {
		this.params = qlParams;
	}
	
	public int getMaxResults() {
		return maxResults;
	}
	
	/**
	 * It means the page size
	 * 
	 * @param maxResults
	 */
	public void setMaxResults(int maxResults) {
		this.maxResults = maxResults;
	}
	
	public boolean hasSort() {
		return compiledSort != null && compiledSort.length() > 0;
	}
	
	public String getSortAndDir() {
		if (compiledSort == null || compiledSort.length() == 0) {
			return "";
		} else if (compiledSort.indexOf(',') == -1) {
			return compiledSort + ' ' + dir;
		} else {
			StringBuilder sb = new StringBuilder();
			String[] sorts = StringUtils.split(compiledSort, ',');
			String[] dirs = StringUtils.split(dir, ',');
			int index = 0;
			for (String sort : sorts) {
				if (sb.length() > 0) {
					sb.append(',');
				}
				String theDir = dirs.length > index ? dirs[index] : dirs[dirs.length - 1];
				sb.append(sort).append(' ').append(theDir);
				index++;
			}
			return sb.toString();
		}
	}

	/**
	 * NOTICE: Use qlParse.setSort to set the sort field
	 * 
	 * @param sort
	 */
	public void setCompiledSort(String compiledSort) {
		this.compiledSort = compiledSort;
	}
	
	public void setSort(QLParse qlParse, String modelName, Object model, String sort) {
		qlParse.setSort(this, modelName, model, sort);
	}

	public void setDir(String dir) {
		if (dir != null && dir.length() > 0) {
			// avoid SQL injection
			int index = dir.indexOf(',');
			
			if (index == -1) {
				if (dir.equals("desc") || dir.equals("DESC")) {
					this.dir = "desc";
				} else {
					this.dir = "asc";
				}
			} else {
				String[] dirs = StringUtils.split(dir, ',');
				for (String theDir : dirs) {
					if (!theDir.equals("desc") && !theDir.equals("DESC")) {
						logger.error("Wrong sort field:" + dir);
						this.dir = "asc";
						return;
					}
				}
				this.dir = dir;
			}
		}
	}

	public boolean isWithPermissionFilter() {
		return permissionFilterExisted;
	}

	public void setDisablePermissionFilter(boolean disable) {
		this.permissionFilterExisted = disable;
	}

	public void update(int start, int limit, int defaultPageSize, String sort, String dir
			, QLParse qlParse, Object model, boolean newSearch, PageElements ps) {
		if (start == 0 && limit == 0) {
			limit = defaultPageSize;
		} else {
			int maxEnabledSize = defaultPageSize * 10;
			if (maxEnabledSize > 0 && limit - start > maxEnabledSize) {
				if (logger.isInfoEnabled()) {
					logger.warn("Search too much, there're performance problems! limit the total size to " + maxEnabledSize
							 + ", the original limit is " + limit + ", starts from " + start);
				}
				limit = start + maxEnabledSize;
			}
		}
		setFirstResult(start);
		setMaxResults(limit);
		
		String modelName = ClassUtil.getClassName(model);
		// TODO: is it good design? move the logic to InvocationUtil.getDefaultParameters?
		if (newSearch && ps != null && StringUtils.isEmpty(sort)) {
			// try to load the default sort from pageElements
    		List<PageElement> elements = ps.findElementsByType("pageinfo");
    		PageElement pageElement = null;
    		if (elements.size() == 1) {
    			pageElement = elements.get(0);
    		} else if (elements.size() > 1) {
    			for (PageElement ele : elements) {
    				if (modelName.equals(ele.getExtraParam("model"))) {
    					pageElement = ele;
    					break;
    				}
    			}
    		}
    		if (pageElement != null) {
    			sort = (String)pageElement.getExtraParam("sort");
    			dir = (String)pageElement.getExtraParam("dir");
    		}
		}
		
		qlParse.setSort(this, modelName, model, sort);
		setDir(dir);
		
		ActionContext context = ActionContext.getContext();
		if (context != null) {
			ValueStack vs = context.getValueStack();
			vs.setValue("sort", sort);
			vs.setValue("dir", this.dir);
		}
	}
	
	public int getDefaultCount() {
		return defaultCount;
	}

	public void setDefaultCount(int defaultCount) {
		this.defaultCount = defaultCount;
	}

	/**
	 * replace '?' in hql using the params
	 * @return
	 */
	public String toString() {
		if (ql == null) return null;

		if (params == null || params.size() == 0) {
			return ql;
		}

		StringBuffer hqlBuf = new StringBuffer(ql.length() + params.size() * 10);
		int index = 0;
		int lastIndex = 0;
		Iterator<Object> paramsIter = params.iterator();
		while(index >= 0 && paramsIter.hasNext()) {
			lastIndex = index;
			index = ql.indexOf('?', lastIndex);
			if (index >= 0) {
				hqlBuf.append(ql.substring(lastIndex, index));
				index++;
			} else {
				hqlBuf.append(ql.substring(lastIndex));
			}

			// FIXME: the ? isn't match to hqlParams
			Object param = paramsIter.next();
			String value = param == null ? "" : param.toString();
			// replace the unsafe char,
            // NOTICE:it's only valid in oracle db
            // if you use it in other db, please repair this.
			value = StringUtils.replace(value, "'", "''");

			// remove it because the HqlParser convert the object to string
			//boolean isStringType;
			//if (param instanceof String) {
			//	isStringType = true;
				hqlBuf.append('\'');
			//} else {
			//	isStringType = false;
			//}
			hqlBuf.append(value);
			//if (isStringType) {
				hqlBuf.append('\'');
			//}
		}
		if (index > 0) {
			hqlBuf.append(ql.substring(index));
		}

		return hqlBuf.toString();
	}
}